/**
 *
 */
package org.bindinghelper;

import java.beans.IntrospectionException;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

class ObservableMethodInterceptor<T> implements MethodInterceptor, Serializable
{
    private static final long serialVersionUID = 0L;
    private static final String WRITE_REPLACE_METHOD_NAME = "writeReplace";
    private static final String GETTER_PREFIX0 = "get";
    private static final String GETTER_PREFIX1 = "is";
    private static final String SETTER_PREFIX = "set";
    private static final Set<Method> OBSERVABLE_METHODS = new HashSet<Method>(Arrays.asList(IObservable.class.getMethods()));

    private Class<T> _originalObjectClass;
    private PropertyChangeSupport _pcs = null;

    public void initPCS(T proxy, Class<T> objectClass)
    {
        _pcs = new PropertyChangeSupport(proxy);
        _originalObjectClass = objectClass;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable
    {
        Object result;
        String methodName = method.getName();

        if(OBSERVABLE_METHODS.contains(method))
        {
            result = PropertyChangeSupport.class.getMethod(methodName, method.getParameterTypes()).invoke(_pcs, args);
        }
        else if(methodName.equals(WRITE_REPLACE_METHOD_NAME))
        {
            result = serialize((T)proxy);
        }
        else
        {
            Object oldValue = null;
            boolean isSetter = methodIsSetter(methodName);
            char[] propertyName = null;
            String methodSuffix = null;

            if (isSetter)
            {
                methodSuffix = methodName.substring(3);
                propertyName = methodSuffix.toCharArray();
                propertyName[0] = Character.toLowerCase(propertyName[0]);

                try
                {
                    oldValue = proxy.getClass().getMethod(GETTER_PREFIX0 + methodSuffix, new Class[] {  }).invoke(proxy, (Object[]) null);
                }
                catch (Throwable t)
                {
                    if(method.getParameterTypes()[0].equals(Boolean.class)
                            || method.getParameterTypes()[0].equals(boolean.class))
                    {
                        try
                        {
                            oldValue = proxy.getClass().getMethod(GETTER_PREFIX1 + new String(methodSuffix), new Class[] {  }).invoke(proxy, (Object[]) null);
                        }
                        catch (Throwable tt)
                        {
                            isSetter = false;
                        }
                    }
                    else
                    {
                        isSetter = false;
                    }
                }
            }

            result =  methodProxy.invokeSuper(proxy, args);

            if (isSetter && (_pcs != null))
            {
                _pcs.firePropertyChange(new String(propertyName), oldValue, args[0]);
            }
        }

        return result;
    }

    private boolean methodIsSetter(String methodName)
    {
        return methodName.startsWith(SETTER_PREFIX);
    }

    private T serialize(T fromObject) throws InstantiationException, IllegalAccessException, IllegalArgumentException, IntrospectionException, InvocationTargetException
    {
        T ret = _originalObjectClass.newInstance();
        BeanUtil.copyBeanData(fromObject, ret, _originalObjectClass);
        return ret;
    }
}